#include "ErrorNoGen.h"
#include <cctype>
#include <sstream>
#include <fstream>
#include <iostream>
#include "nlohmann/json.hpp"
using json = nlohmann::json;

///json file format
// {
//   "className": "otc_hist_err",
//   "errorList":[
//      {
//          "errorId":"success",
//          "errorMsg": "success",
//          "errorNo":0       
//      },
//      {
//          "errorId":"io_error",
//          "errorMsg": "can not open file",
//          "errorNo": 1 
//      }
//   ]
// }
// 
//
//
void ErrorNoGen::genFiles(const std::string& fileName)
{
    if (!init(fileName)) {
        return;
    }
    ///
    std::ofstream ofs(headFileName);
    ofs << genFileHeaderString() 
        << genIncludeString()
        << genMultiPairErrorString()
        << genErrorDefClassString()
        << genFileFooterString();
    std::ofstream ofs_cpp(cppFileName);
    ofs_cpp << genErrorImplClassString();
    ofs.close();
    ofs_cpp.close();  
}

bool ErrorNoGen::init(const std::string& fileName)
{
    std::ifstream ifs;
    ifs.open(fileName);
    if (!ifs.is_open()) {
        std::cout << "open " << fileName << " failed" << std::endl;
        return false;
    }
    json fileInfo = json::parse(ifs);
    if (fileInfo.find("className") == fileInfo.end()) {
        std::cout << "className field not found" << std::endl;
        return false;
    } else {
        className = fileInfo["className"].get<std::string>();
    }
    if (fileInfo.find("errorList") == fileInfo.end()) {
        std::cout << "errorList field not found" << std::endl;
        return false;
    } else {
        std::vector<json> errJsonList = fileInfo["errorList"].get<std::vector<json>>();
        for (json& ele : errJsonList) {
            if (ele.find("errorId") == ele.end() || 
                ele.find("errorNo") == ele.end() ||
                ele.find("errorMsg") == ele.end()) {
                std::cout << "errorId field or errorNo field or errorMsg field not found" << std::endl;
                continue;
            } else {
                ErrorInfo info;
                info.errorId = strToUpper(ele["errorId"].get<std::string>());
                info.errorNo = ele["errorNo"].get<int32_t>();
                info.errorMsg = ele["errorMsg"].get<std::string>();
                errIdVec.push_back(info); 
            }
        }
    }
    if (errIdVec.empty()) {
        std::cout << "errorList content is empty" << std::endl;
        return false;
    }
    return true;
}
std::string ErrorNoGen::genMultiPairErrorString()
{
    std::ostringstream oss;
    for (const ErrorInfo& err: errIdVec) {
        oss << genOnePairErrorString(err.errorId, err.errorNo, err.errorMsg);
    }
    return oss.str();
}

std::string ErrorNoGen::genOnePairErrorString(const std::string &errorId, const int32_t errorNo, const std::string& errorMsg)
{
    std::ostringstream oss;
    oss << "const int32_t     " << errorId << "     = " << errorNo << ";\n"
        << "const std::string " << errorId << "_MSG = \"" << errorMsg << "\";\n\n";
    return oss.str();
}

std::string ErrorNoGen::strToUpper(const std::string &str)
{
    std::string result;
    result.reserve(str.size());
    for (auto ch : str)
    {
        result.push_back(std::toupper(ch));
    }
    return result;
}

std::string ErrorNoGen::genErrorDefClassString()
{
    std::ostringstream oss;
    oss << "class " << className << "\n"
        << "{\n"
        << "public:\n"
        << "    static const std::string getMsg(const int32_t code)\n"
        << "    {\n"
        << "        auto it = dataMap.find(code);\n"
        << "        if (it != dataMap.end()) {\n"
        << "            return it->second;\n"
        << "        }\n"
        << "        return \"\";\n"
        << "    }\n"
        << "private:\n"
        << "    static const std::unordered_map<int32_t, std::string> dataMap;\n"
        << "};\n";
    return oss.str();
}

std::string ErrorNoGen::genErrorImplClassString()
{
    std::ostringstream oss;
    oss << "#include \"" << headFileName << "\"\n"
        << "const std::unordered_map<int32_t, std::string> " << className << "::dataMap = {\n";
    for (int i = 0; i < errIdVec.size(); ++i) {
        if (i != errIdVec.size() - 1) {
            oss << "    {" << errIdVec[i].errorId << ", " << errIdVec[i].errorId << "_MSG},\n";
        } else {
            oss << "    {" << errIdVec[i].errorId << ", " << errIdVec[i].errorId << "_MSG}\n";  
        }   
    }
    oss << "};\n";
    return oss.str();
}

std::string ErrorNoGen::genIncludeString()
{
    std::ostringstream oss;
    oss << "#include <string>\n"
        << "#include <cstdint>\n"
        << "#include <unordered_map>\n";
    return oss.str();
}

std::string ErrorNoGen::genFileHeaderString()
{
    std::ostringstream oss;
    oss << "#ifndef ERROR_NO_DEF_H_\n"
        << "#define ERROR_NO_DEF_H_\n";
    return oss.str();
}

std::string ErrorNoGen::genFileFooterString()
{
    std::ostringstream oss;
    oss << "#endif ///< ERROR_NO_DEF_H_ \n";
    return oss.str();
}
